package com.atguigu.tingshu.payment.service.impl;

import cn.hutool.core.lang.hash.Hash;
import cn.hutool.core.util.IdUtil;
import com.atguigu.tingshu.account.AccountFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.account.RechargeInfo;
import com.atguigu.tingshu.model.payment.PaymentInfo;
import com.atguigu.tingshu.order.client.OrderFeignClient;
import com.atguigu.tingshu.payment.config.WxPayV3Config;
import com.atguigu.tingshu.payment.service.PaymentInfoService;
import com.atguigu.tingshu.payment.service.WxPayService;
import com.atguigu.tingshu.payment.util.PayUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import io.seata.spring.annotation.GlobalTransactional;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {

    @Autowired
    private PaymentInfoService paymentInfoService;

    @Autowired
    private RSAAutoCertificateConfig rsaAutoCertificateConfig;

    @Autowired
    private WxPayV3Config wxPayV3Config;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private OrderFeignClient orderFeignClient;

    @Autowired
    private AccountFeignClient accountFeignClient;

    /**
     * 获取微信支付所需参数
     *
     * @param paymentType 支付类型：订单、充值
     * @param orderNo     订单编号
     * @return {}小程序微信支付拉起参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, String> getMiniProgramPayParam(String paymentType, String orderNo) {
        try {
            //1.获取或保存本地交易记录
            PaymentInfo paymentInfo = paymentInfoService.savePaymentInfo(paymentType, orderNo);
            if (paymentInfo != null && SystemConstant.PAYMENT_STATUS_UNPAID.equals(paymentInfo.getPaymentStatus())) {
                //2.调用微信支付接口获取小程序所需支付参数
                //2.1 构建JSAPI支付业务service
                JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(rsaAutoCertificateConfig).build();
                //2.2 创建预支付请求对象
                PrepayRequest request = new PrepayRequest();
                Amount amount = new Amount();
                amount.setTotal(1);//TODO 开发中将应付金额设置为1分
                request.setAmount(amount);
                request.setAppid(wxPayV3Config.getAppid());
                request.setMchid(wxPayV3Config.getMerchantId());
                request.setDescription(paymentInfo.getContent());
                //支付成功后，微信回调支付结果接口地址
                request.setNotifyUrl(wxPayV3Config.getNotifyUrl());
                request.setOutTradeNo(paymentInfo.getOrderNo());
                //小程序未上线之前，只允许应用下开发开发者账户进行支付。开发阶段 设置付款者信息
                Payer payer = new Payer();
                payer.setOpenid("odo3j4qp-wC3HVq9Z_D9C0cOr0Zs");//微信账户唯一标识
                request.setPayer(payer);
                //2.3 调用微信支付接口，获取预支付参数
                PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
                if (response != null) {
                    String timeStamp = response.getTimeStamp();
                    String nonceStr = response.getNonceStr();
                    String packageVal = response.getPackageVal();
                    String signType = response.getSignType();
                    String paySign = response.getPaySign();
                    Map<String, String> map = new HashMap<>();
                    map.put("timeStamp", timeStamp);
                    map.put("nonceStr", nonceStr);
                    map.put("package", packageVal);
                    map.put("signType", signType);
                    map.put("paySign", paySign);
                    return map;
                }
            }
            return null;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据商户侧订单编号查询微信支付状态
     *
     * @param orderNo
     * @return
     */
    @Override
    public Boolean queryPayStatus(String orderNo) {
        //1.创建小程序支付业务service
        //JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(rsaAutoCertificateConfig).build();
        ////2.创建根据商户订单编号查询支付状态请求对象
        //QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
        //request.setMchid(wxPayV3Config.getMerchantId());
        //request.setOutTradeNo(orderNo);
        ////3.调用微信查询支付结果接口获取交易对象，解析支付结果
        //Transaction transaction = service.queryOrderByOutTradeNo(request);
        //if (transaction != null) {
        //    Transaction.TradeStateEnum tradeState = transaction.getTradeState();
        //    if (Transaction.TradeStateEnum.SUCCESS == tradeState) {
        //        return true;
        //    }
        //}
        //return false;

        //TODO 其他同学，默认返回true（用户已经完成付款），表示支付成功，在这个方法中模拟支付成功后业务操作：1.修改本地交易记录状态 2.订单状态修改或充值状态 3.虚拟物品发货或充值业务
        //1.1 模拟微信交易对象
        Transaction transaction = new Transaction();
        transaction.setOutTradeNo(orderNo);
        transaction.setTransactionId("wx"+ IdUtil.getSnowflakeNextId());
        this.updatePaymentInfo(transaction);
        return true;
    }

    /**
     * 用户微信付款后，微信调用该接口通知商户用户支付结果
     *
     * @param request
     * @return
     */
    @Override
    public Map<String, String> paySuccessNotify(HttpServletRequest request) {
        //1.获取请求对象中的请求体跟请求头提交参数用于验签以及解密请求体明文参数
        String signature = request.getHeader("Wechatpay-Signature");
        String serial = request.getHeader("Wechatpay-Serial");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String signaureType = request.getHeader("Wechatpay-Signature-Type");
        log.info("签名：{}，序列号：{}，随机数：{}，时间戳：{}，签名类型：{}", signature, serial, nonce, timestamp, signaureType);
        //1.2 获取请求体中参数
        String body = PayUtil.readData(request);
        log.info("请求体：{}", body);

        //2.验证签名：防止出现假通知，造成商户损失
        //2.1 创建请求参数对象封装请求头跟请求体相关参数
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(serial)
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                .body(body)
                .build();
        //2.2 初始化NotificationParser解析器对象
        NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
        //2.3 调用解析器对象验签、解密并转换成 Transaction
        Transaction transaction = parser.parse(requestParam, Transaction.class);
        if (transaction != null) {
            //3.幂等性处理 获取支付结果相关业务数据：微信交易订单编号
            Transaction.TradeStateEnum tradeState = transaction.getTradeState();
            if (Transaction.TradeStateEnum.SUCCESS == tradeState) {
                //3.1 获取微信支付订单编号
                String transactionId = transaction.getTransactionId();
                //3.2 利用Redis命令set nx命令进行幂等性处理
                String key = "wexin:" + transactionId;
                Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, transactionId, 25, TimeUnit.HOURS);
                if (flag) {
                    try {
                        //4.验证商户侧金额跟支付结果中金额
                        Integer payerTotal = transaction.getAmount().getPayerTotal();
                        if (payerTotal.intValue() == 1) {
                            //5.核心业务处理
                            this.updatePaymentInfo(transaction);
                            Map<String, String> map = new HashMap<>();
                            map.put("code", "SUCCESS");
                            map.put("message", "成功");
                            return map;
                        }
                    } catch (Exception e) {
                        redisTemplate.delete(key);
                        throw new RuntimeException(e);
                    }
                }
            }
        }
        return null;
    }

    /**
     * 全局事务发起方
     * 用户付款成功后，更新本地交易记录以及对应订单或者充值记录
     *
     * @param transaction
     */
    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public void updatePaymentInfo(Transaction transaction) {
        //1.核心业务1：更新本地交易记录状态：已支付
        //1.1 从交易对象中获取商户订单编号
        String orderNo = transaction.getOutTradeNo();
        //1.2 根据订单编号查询本地交易记录
        LambdaQueryWrapper<PaymentInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PaymentInfo::getOrderNo, orderNo);
        PaymentInfo paymentInfo = paymentInfoService.getOne(queryWrapper);
        String paymentStatus = paymentInfo.getPaymentStatus();
        if (SystemConstant.PAYMENT_STATUS_PAID.equals(paymentStatus)) {
            //本地交易记录状态如果是已支付，直接返回
            return;
        }
        //1.3 更新本地交易记录状态为已支付，更新回调时间、内容、微信交易编号
        paymentInfo.setPaymentStatus(SystemConstant.PAYMENT_STATUS_PAID);
        paymentInfo.setOutTradeNo(transaction.getTransactionId());
        paymentInfo.setCallbackTime(new Date());
        paymentInfo.setCallbackContent(transaction.toString());
        paymentInfoService.updateById(paymentInfo);

        //2.核心业务2：判断本地交易记录支付类型：处理订单 支付类型：1301-订单 1302-充值
        //支付类型：1301-订单 1302-充值
        String paymentType = paymentInfo.getPaymentType();
        if (SystemConstant.PAYMENT_TYPE_ORDER.equals(paymentType)) {
            //2.1 远程调用订单服务，处理支付成功订单业务
            Result result = orderFeignClient.orderPaySuccess(orderNo);
            //2.2 判断响应结果对象中code是否200 如果不是，进行全局事务回滚
            if (result.getCode().intValue() != 200) {
                throw new GuiguException(500, "远程调用订单服务失败");
            }
        }
        if (SystemConstant.PAYMENT_TYPE_RECHARGE.equals(paymentType)) {
            //3.核心业务3：判断本地交易记录支付类型：处理充值
            //3.1 远程调用账户服务，处理充值业务
            Result result = accountFeignClient.rechargePaySuccess(orderNo);
            //3.2 判断响应结果对象中code是否200 如果不是，进行全局事务回滚
            if (result.getCode().intValue() != 200) {
                throw new GuiguException(500,"远程调用账户服务失败");
            }
        }
    }
}
